﻿/* System.Compute: a Computation Framework for .NET/Mono
 * Copyright (C) 2011 Ralf "raboni" Boeckhorst
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 3 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General
 * Public License along with this library; if not, write to the
 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA 02110-1301 USA
 */

using System.Runtime.InteropServices;

namespace System.Compute.OpenCL
{
    public sealed class Event : ICloneable, IDisposable
    {
        delegate void pfnNotify(IntPtr @event, CommandExecutionStatus event_command_exec_status, byte[] user_data);

        public delegate void Callback(Event @event, CommandExecutionStatus event_command_exec_status, byte[] user_data);

        [DllImport("opencl.dll", ExactSpelling = true)]
        static extern ErrorCode clWaitForEvents(uint num_events, IntPtr[] event_list);

        [DllImport("opencl.dll", ExactSpelling = true)]
        static extern ErrorCode clGetEventInfo(IntPtr @event, EventInfo param_name, SizeT param_value_size, ref IntPtr param_value, SizeT param_value_size_ret);

        [DllImport("opencl.dll", ExactSpelling = true)]
        static extern ErrorCode clGetEventInfo(IntPtr @event, EventInfo param_name, SizeT param_value_size, ref CommandType param_value, SizeT param_value_size_ret);

        [DllImport("opencl.dll", ExactSpelling = true)]
        static extern ErrorCode clGetEventInfo(IntPtr @event, EventInfo param_name, SizeT param_value_size, ref CommandExecutionStatus param_value, SizeT param_value_size_ret);

        [DllImport("opencl.dll", ExactSpelling = true)]
        static extern ErrorCode clGetEventInfo(IntPtr @event, EventInfo param_name, SizeT param_value_size, ref uint param_value, SizeT param_value_size_ret);

        [DllImport("opencl.dll", ExactSpelling = true)]
        static extern ErrorCode clGetEventProfilingInfo(IntPtr @event, ProfilingInfo param_name, SizeT param_value_size, ref long param_value, SizeT param_value_size_ret);

        [DllImport("opencl.dll", ExactSpelling = true)]
        static extern ErrorCode clRetainEvent(IntPtr @event);

        [DllImport("opencl.dll", ExactSpelling = true)]
        static extern ErrorCode clReleaseEvent(IntPtr @event);

        [DllImport("opencl.dll", ExactSpelling = true)]
        static extern IntPtr clCreateUserEvent(IntPtr context, ref ErrorCode errcode_ret);

        [DllImport("opencl.dll", ExactSpelling = true)]
        static extern ErrorCode clSetUserEventStatus(IntPtr @event, UserEventStatus status);

        [DllImport("opencl.dll", ExactSpelling = true)]
        static extern ErrorCode clSetEventCallback(IntPtr @event, CommandExecutionStatus command_exec_callback_type, pfnNotify notify, byte[] user_data);

        internal IntPtr Handle;

        public CommandQueue CommandQueue
        {
            get
            {
                IntPtr ret = IntPtr.Zero;
                ErrorCode res = clGetEventInfo(Handle, EventInfo.CommandQueue, IntPtr.Size, ref ret, 0);
                if (res != ErrorCode.Success)
                    throw new OpenCLException(res);
                return new CommandQueue(ret);
            }
        }

        public CommandType CommandType
        {
            get
            {
                CommandType ret = 0;
                ErrorCode res = clGetEventInfo(Handle, EventInfo.CommandType, sizeof(CommandType), ref ret, 0);
                if (res != ErrorCode.Success)
                    throw new OpenCLException(res);
                return ret;
            }
        }

        public CommandExecutionStatus ExecutionStatus
        {
            get
            {
                CommandExecutionStatus ret = 0;
                ErrorCode res = clGetEventInfo(Handle, EventInfo.CommandExecutionStatus, sizeof(CommandExecutionStatus), ref ret, 0);
                if (res != ErrorCode.Success)
                    throw new OpenCLException(res);
                return ret;
            }
        }

        public uint ReferenceCount
        {
            get
            {
                uint ret = 0;
                ErrorCode res = clGetEventInfo(Handle, EventInfo.ReferenceCount, sizeof(uint), ref ret, 0);
                if (res != ErrorCode.Success)
                    throw new OpenCLException(res);
                return ret;
            }
        }

        public long CommandQueued
        {
            get
            {
                long ret = 0;
                ErrorCode res = clGetEventProfilingInfo(Handle, ProfilingInfo.CommandQueued, sizeof(long), ref ret, 0);
                if (res == ErrorCode.ProfilingInfoNotAvailable)
                    return -1;
                else if (res != ErrorCode.Success)
                    throw new OpenCLException(res);
                return ret;
            }
        }

        public long CommandSubmit
        {
            get
            {
                long ret = 0;
                ErrorCode res = clGetEventProfilingInfo(Handle, ProfilingInfo.CommandSubmit, sizeof(long), ref ret, 0);
                if (res == ErrorCode.ProfilingInfoNotAvailable)
                    return -1;
                else if (res != ErrorCode.Success)
                    throw new OpenCLException(res);
                return ret;
            }
        }

        public long CommandStart
        {
            get
            {
                long ret = 0;
                ErrorCode res = clGetEventProfilingInfo(Handle, ProfilingInfo.CommandStart, sizeof(long), ref ret, 0);
                if (res == ErrorCode.ProfilingInfoNotAvailable)
                    return -1;
                else if (res != ErrorCode.Success)
                    throw new OpenCLException(res);
                return ret;
            }
        }

        public long CommandEnd
        {
            get
            {
                long ret = 0;
                ErrorCode res = clGetEventProfilingInfo(Handle, ProfilingInfo.CommandEnd, sizeof(long), ref ret, 0);
                if (res == ErrorCode.ProfilingInfoNotAvailable)
                    return -1;
                else if (res != ErrorCode.Success)
                    throw new OpenCLException(res);
                return ret;
            }
        }

        internal Event(IntPtr handle)
        {
            Handle = handle;
        }

        public Event(Context context)
        {
            ErrorCode res = ErrorCode.Success;
            Handle = clCreateUserEvent(context.Handle, ref res);
            if (res != ErrorCode.Success)
                throw new OpenCLException(res);
        }

        public void Wait()
        {
            ErrorCode res = clWaitForEvents(1, new IntPtr[] { Handle });
            if (res != ErrorCode.Success)
                throw new OpenCLException(res);
        }

        public static void WaitForEvents(params Event[] wait_list)
        {
            IntPtr[] p_wait_list = null;
            if (wait_list.LongLength > 0)
            {
                p_wait_list = new IntPtr[wait_list.LongLength];
                for (long i = 0; i < wait_list.LongLength; i++)
                    p_wait_list[i] = wait_list[i].Handle;
            }
            ErrorCode res = clWaitForEvents((uint)wait_list.LongLength, p_wait_list);
            if (res != ErrorCode.Success)
                throw new OpenCLException(res);
        }

        public static double ElapsedSeconds(SizeT profilingTimerResolution, long start, long end)
        {
            return (double)(end - start) / (double)profilingTimerResolution * 1E-9;
        }

        public void SetUserEventStatus(UserEventStatus status)
        {
            ErrorCode res = clSetUserEventStatus(Handle, status);
            if (res != ErrorCode.Success)
                throw new OpenCLException(res);
        }

        public void SetEventCallback(CommandExecutionStatus command_exec_callback_type, Callback callback, byte[] user_data)
        {
            pfnNotify notify = (a, b, c) =>
            {
                callback(this, b, c);
            };
            ErrorCode res = clSetEventCallback(Handle, command_exec_callback_type, notify, user_data);
            if (res != ErrorCode.Success)
                throw new OpenCLException(res);
        }

        public object Clone()
        {
            ErrorCode res = clRetainEvent(Handle);
            if (res != ErrorCode.Success)
                throw new OpenCLException(res);
            return new Event(Handle);
        }

        public void Dispose()
        {
            ErrorCode res = clReleaseEvent(Handle);
            if (res != ErrorCode.Success)
                throw new OpenCLException(res);
            Handle = IntPtr.Zero;
        }
    }
}
